# coding: utf-8

from xml.dom.minidom import *

from ala_classes import AlaGrammarTableError


class ACT:
    N = 0
    S = 1
    R = 2
    G = 3
    ACC = 4


class ParserAction(object):
    type = None
    value = None

    def __init__(self, type, value):
        self.type = type
        self.value = value


class GrammarTable(object):
    __symbols = None
    __productions = None
    __table = None

    def __init__(self, xml_file_name):
        self.__symbols = {}
        self.__productions = {}
        self.__table = {}
        self.load_from_xml(xml_file_name)

    # INTERFACE

    def load_from_xml(self, file_name):
        xml_dom = parse(file_name)
        self.__load_symbols(xml_dom.getElementsByTagName('m_Symbol')[0])
        self.__load_productions(xml_dom.getElementsByTagName('m_Production')[0])
        self.__load_table(xml_dom.getElementsByTagName('LALRTable')[0])

    def get_token_index(self, token):
        for i in self.__symbols.keys():
            if self.__symbols[i]['name'] == token.type_string():
                return i
        raise AlaGrammarTableError("Symbol '{0}' was not found".format(token))

    def get_symbol_value(self, symbol_id):
        try:
            return self.__symbols[symbol_id]['name']
        except KeyError:
            raise AlaGrammarTableError("Symbol with id '{0}' was not found".format(symbol_id))

    def get_production_symbol_index(self, production_id):
        try:
            return self.__productions[production_id]['symbol_index']
        except KeyError:
            raise AlaGrammarTableError("Production with index '{0}' was not found".format(production_id))

    def get_production_value(self, production_id):
        try:
            return self.get_symbol_value(self.__productions[production_id]['symbol_index'])
        except KeyError:
            raise AlaGrammarTableError("Table error")

    def get_production_length(self, production_id):
        try:
            return len(self.__productions[production_id]['nodes_indexes'])
        except KeyError:
            raise AlaGrammarTableError("Production with id '{0}' was not found".format(id))

    def get_action(self, state, token):
        try:
            action_dict = self.__table[state][self.get_token_index(token)]
            if action_dict['action'] not in [ACT.S, ACT.R, ACT.ACC]:
                raise KeyError()
            return ParserAction(action_dict['action'], action_dict['value'])
        except KeyError:
            raise AlaGrammarTableError("There is no shift or reduce in cell [{0}][{1}]".format(state, token),
                                       line_number=token.line)

    def get_goto_value(self, state, production_id):
        try:
            goto = self.__table[state][self.get_production_symbol_index(production_id)]
            if goto['action'] != ACT.G:
                raise KeyError()
        except KeyError:
            raise AlaGrammarTableError("There is no goto in cell [{0}][{1}]".format(state, production_id))
        return goto['value']

    # PRIVATE METHODS

    def __load_symbols(self, symbols_dom_element):
        for symbol_node in symbols_dom_element.getElementsByTagName('Symbol'):
            index = int(symbol_node.getAttributeNode('Index').nodeValue)
            name = symbol_node.getAttributeNode('Name').nodeValue
            type = int(symbol_node.getAttributeNode('Type').nodeValue)
            self.__symbols[index] = {'name': name, 'type': type}

    def __load_productions(self, productions_dom_element):
        for production_node in productions_dom_element.getElementsByTagName('Production'):
            index = int(production_node.getAttributeNode('Index').nodeValue)
            symbol_index = int(production_node.getAttributeNode('NonTerminalIndex').nodeValue)
            nodes_indexes = []
            for symbol_node in production_node.getElementsByTagName('ProductionSymbol'):
                nodes_indexes.append(int(symbol_node.getAttributeNode('SymbolIndex').nodeValue))
            self.__productions[index] = {'symbol_index': symbol_index, 'nodes_indexes': nodes_indexes}

    def __load_table(self, table_dom_element):
        for state_node in table_dom_element.getElementsByTagName('LALRState'):
            state_index = int(state_node.getAttributeNode('Index').nodeValue)
            row = {}
            for action_node in state_node.getElementsByTagName('LALRAction'):
                symbol_index = int(action_node.getAttributeNode('SymbolIndex').nodeValue)
                action = int(action_node.getAttributeNode('Action').nodeValue)
                value = int(action_node.getAttributeNode('Value').nodeValue)
                row[symbol_index] = {'action': action, 'value': value}
            self.__table[state_index] = row